#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include "../include/queue.h"
#include "../include/bondi.h"
#include "../include/cityDefs.h"
#include "../include/marshallADT.h"
#include "../include/linea.h"

#define PRANDOM 40 //probabilidad en porcentaje de que aparezca un pasajero
#define LRANDOM 15 //probabilidad en porcentaje de que una linea agregue pasajeros


//parsea el archivo de linea y arma las colas de bondis y de paradas
void parseLinea(FILE * lineaArch, queueADT bondiQueue, queueADT stopsQueue, int * lineaN);

//comunica a la ciudad que agregue los buses
int busInjector(queueADT bondiQueue, coords * stopsCoords, int cantStops);

//comunica a la ciudad que agregue las paradas
int stopsInjector(marshallADT mar, queueADT stopsQueue, coords ** stopsCoords, int * paradaAbs);

//maneja las paradas atendiendo consultas de los bondis
void manageStops(marshallADT marB, marshallADT marC, int cantBond, int cantStops, int lineaN, int * paradaAbs);

//setea pasajeros nuevos de forma aleatoria
void setPassengers(marshallADT marC, stops_t * stops, int cantStops, int lineaN, int * cantPas, int * paradaAbs);

//para el manejo de señales
static int exitFlag = 0;
static void
exitSigInt(int sign) {
	exitFlag = 1;
}

void
manageLinea(FILE * lineaArch) {

	queueADT bondiQueue, stopsQueue;
	marshallADT marC, marB;
	int cantBond, cantStops, lineaN;
	coords * stopsCoords;
	int paradaAbs[MAX_STOPS];

	//handler de la señal de mi padre para cerrarme bien
	static struct sigaction act;
	act.sa_handler = exitSigInt;
	sigfillset(&(act.sa_mask));
	sigaction(SIGUSR1, &act, NULL);

	bondiQueue = openPQueue(sizeof(bondiT));
	stopsQueue = openPQueue(sizeof(stops_t));

	//cargo la cola con bondis, y la cola de paradas del archivo de linea
	parseLinea(lineaArch, bondiQueue, stopsQueue, &lineaN);

	fclose(lineaArch);

	//si no cargue ningun bondi o ninguna parada, salgo de la linea
	if (!isEmpty(bondiQueue) && !isEmpty(stopsQueue)) {
		if((marC = getMarsh((lineaN*2)+10, CITY_ID)) == NULL)
			kill(0, SIGUSR1);
		if((marB = openMarsh((lineaN*2)-1+10)) == NULL)
			kill(0, SIGUSR1);

		//agrego las paradas y los bondis al mapa (avisandole a la ciudad)
		cantStops = stopsInjector(marC, stopsQueue, &stopsCoords, paradaAbs);
		cantBond = busInjector(bondiQueue, stopsCoords, cantStops);

		//manejo los pasajeros en las paradas, respondo pedidos de buses
		manageStops(marB, marC, cantBond, cantStops, lineaN, paradaAbs);

		closeMarsh(marB);
		closeMarsh(marC);
	}

	exit(1);
}

void
manageStops(marshallADT marB, marshallADT marC, int cantBond, int cantStops, int lineaN, int * paradaAbs){

	char mes;
	int i, cantEnPar, cantAbord, nBond, nPar;
	busStats * bondis;
	stopReqT stopR;
	stops_t * stops;

	srand(time(NULL));

	//arreglo interno de estado de bondis
	if((bondis = calloc(cantBond, sizeof(busStats)))==NULL)
		exit(1);

	//arreglo interno de paradas
	if((stops = calloc(cantStops, sizeof(stops_t)))==NULL)
		exit(1);

	int cantPas = 0;

	while (!exitFlag){
		if(recvData(marB, &stopR, LINE)!=-1){


		if(rand() % 100 < PRANDOM)
			setPassengers(marC, stops, cantStops, lineaN, &cantPas, paradaAbs);

		mes = DONTSTOP;
		//levanto el numero de bondi y el de parada recibido en la consulta
		nBond = stopR.nrobondi-1;
		nPar = stopR.parada-1;

		cantAbord = bondis[nBond].cantPasAbordo;

		//recorrer pasajeros del bondi para ver si alguien se quieren bajar, y bajarlo
		for(i=0; i < cantAbord && mes == DONTSTOP; i++){
			if((bondis[nBond].passengers[i].paradaDest) == paradaAbs[nPar]){
				//aviso a la ciudad para el log
				bondis[nBond].passengers[i].status = P_G_DOWN;
				bondis[nBond].passengers[i].bondi = nBond +1;
				sendData(marC, &bondis[nBond].passengers[i], PASSENGER);

				bondis[nBond].passengers[i] = bondis[nBond].passengers[cantAbord - 1];
				cantAbord--;
				bondis[nBond].cantPasAbordo = cantAbord;
				mes += P_G_DOWN;
			}
		}

		cantEnPar = stops[nPar].cant_pas;

		//recorrer pasajeros de la parada y subir uno
		for(i=0; i < cantEnPar && (mes < P_G_UP); i++){
			if(cantAbord < MAX_PAS){
				//aviso a la ciudad para el log
				stops[nPar].passengers[i].status = P_G_UP;
				stops[nPar].passengers[i].bondi = nBond +1;
				sendData(marC, &stops[nPar].passengers[i], PASSENGER);

				bondis[nBond].passengers[cantAbord] = stops[nPar].passengers[i];
				stops[nPar].passengers[i] = stops[nPar].passengers[cantEnPar-1];
				cantEnPar--;
				stops[nPar].cant_pas = cantEnPar;
				cantAbord++;
				bondis[nBond].cantPasAbordo = cantAbord;
				mes += P_G_UP;
			}
		}

		//aviso al bus si subioi o bajo un pasajero, o ambas
		sendData(marB, &mes, LINE);
	}
	}
}

void
setPassengers(marshallADT marC, stops_t * stops, int cantStops, int lineaN, int * cantPas, int * paradaAbs){
	int nuevaCant, i, j;

	for(i = 0; i<cantStops; i++){

		if((rand() % 100) < LRANDOM){
			nuevaCant = stops[i].cant_pas + rand() %2;			//random para la cant de pasajeros en la parada

			for(j = stops[i].cant_pas; j < nuevaCant && j < MAX_PAS-1; j++){
				stops[i].passengers[j].paradaOr = paradaAbs[i];
				do {
					stops[i].passengers[j].paradaDest = paradaAbs[rand() % (cantStops)];		//random para el destino
				}while(stops[i].passengers[j].paradaOr == stops[i].passengers[j].paradaDest); 	//mientras el origen sea igual al destino
				stops[i].passengers[j].linea = lineaN;
				stops[i].passengers[j].bondi = 0;
				stops[i].passengers[j].status = WAIT;
				stops[i].passengers[j].numeroPas = ++(*cantPas);
				stops[i].passengers[j].type = PASSENGER;

				//aviso a la ciudad para el log
				sendData(marC, &stops[i].passengers[j], PASSENGER);
			}
			stops[i].cant_pas = nuevaCant;
		}
	}
}

int
busInjector(queueADT bondiQueue, coords * stopsCoords, int cantStops){
	bondiT * bondi;
	int cantBond = 0;

	//mientras haya elementos en la cola, los proceso
	while (!isEmpty(bondiQueue) && !exitFlag){
		bondi = getElem(bondiQueue); //saco el elemento de la cola

		if ( fork() == 0) {
			manageBondi(bondi, stopsCoords, cantStops);
		}
		cantBond++;
		free(bondi);
	}

	freePQueue(bondiQueue);
	return cantBond;
}

int
stopsInjector(marshallADT mar, queueADT stopsQueue, coords ** stopsCoords, int * paradaAbs){

	stops_t * stop;
	int cantStops = 0;
	int nParAbs;
	coords * stops;

	if((stops=malloc(sizeof(coords)*MAX_PAS))==NULL)
		exit(1);

	//mientras haya paradas en la cola las proceso, avisandole a la ciudad
	while (!isEmpty(stopsQueue) && cantStops < MAX_STOPS){
		stop = getElem(stopsQueue); //saco el elemento de la cola

		stops[cantStops].x = stop->pos.x;
		stops[cantStops].y = stop->pos.y;

		sendData(mar, stop, STOP);

		recvData(mar, &nParAbs, STOP);

		if(nParAbs != -1){
			//guardo en mi arreglo de numeros absolutos, el valor devuelto por la ciudad
			paradaAbs[cantStops] = nParAbs;
			cantStops++;
		}else{
			fprintf(stderr, "Parada no seteada - Posicion invalida\n");
		}

		free(stop);
	}

	*stopsCoords = stops;

	freePQueue(stopsQueue);
	return cantStops;
}

void
parseLinea(FILE * lineaArch, queueADT bondiQueue, queueADT stopsQueue, int * lineaN) {
	bondiT bondi;
	stops_t stop;

	stop.type = STOP;

	int cant, aux1, aux2;

	cant = fscanf(lineaArch, "linea:%d\n", &aux1);
	if (cant != 1) {
		return;
	}
	bondi.linea = aux1;
	stop.linea = aux1;
	*lineaN = aux1;

	cant = fscanf(lineaArch, "posini:(%d,%d)\n", &aux1, &aux2);
	if (cant != 2) {
		return;
	}
	bondi.curPos.x = aux1+1;		//+1 por el encuandrado de FORBIDDENs
	bondi.curPos.y = aux2+1;

	cant = fscanf(lineaArch, "sentini:%d\n", &aux1);
	if (cant != 1) {
		return;
	}
	bondi.sentidoActual = aux1;

	cant = fscanf(lineaArch, "longrec:%d\nrec:", &(bondi.longTrayecto));
	if (cant != 1) {
		return;
	}

	int i;
	for (i = 0; i < bondi.longTrayecto; i++) {
		cant = fscanf(lineaArch, "%d;", &aux1);
		if (cant != 1) {
			return;
		}
		bondi.trayecto[i] = aux1;
	}

	int cantStops;
		cant = fscanf(lineaArch, "\ncantparadas:%d\nparadas:", &cantStops);
		if (cant != 1) {
			return;
		}

		//armo la cola de paradas, con la informacion levantada del archivo
		for (i = 0; i < cantStops; i++) {
			cant = fscanf(lineaArch, "(%d,%d);", &aux1, &aux2);
			if (cant == 2) {
				stop.pos.x = aux1+1;		//+1 por el encuandrado de FORBIDDENs
				stop.pos.y = aux2+1;
				stop.paradaNInterno = stop.linea *10 + i + 1;
				addElem(stopsQueue, &stop);
			}
		}

	int cantBondi;
	cant = fscanf(lineaArch, "\ncantbondi:%d\nbondis:", &cantBondi);
	if (cant != 1) {
		return;
	}

	//armo la cola de buses, con la informacion levantada del archivo
	for (i = 0; i < cantBondi; i++) {
		cant = fscanf(lineaArch, "(%d,%d,%d);", &aux1,
			&(bondi.horaInicio), &aux2);
		if (cant == 3) {
			bondi.bondi = aux1;
			bondi.vel = aux2;
			bondi.indice = 0;
			addElem(bondiQueue, &bondi);
		}
	}

	return;
}
